package com.artnology.independence.views;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JComponent;

import org.eclipse.albireo.core.SwingControl;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IOperationApprover;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.LinearUndoEnforcer;
import org.eclipse.core.commands.operations.ObjectUndoContext;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.BooleanPropertyAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.operations.RedoActionHandler;
import org.eclipse.ui.operations.UndoActionHandler;
import org.eclipse.ui.part.ViewPart;

import com.artnology.independence.Activator;
import com.artnology.independence.model.DVGraphModel;
import com.artnology.independence.model.DVNode;
import com.artnology.independence.model.DVPackage;
import com.artnology.independence.model.DependencyGraph;
import com.artnology.independence.model.relationships.DVExtensionRelationship;
import com.artnology.independence.model.relationships.DVImplementsRelationship;
import com.artnology.independence.model.relationships.DVOneToManyRelationship;
import com.artnology.independence.model.relationships.DVOneToOneRelationship;
import com.artnology.independence.model.relationships.DVReference;
import com.artnology.independence.model.relationships.DVUsesRelationship;
import com.artnology.independence.views.operations.AddNodeToGraphOperation;
import com.artnology.independence.views.operations.RemoveNodeFromGraphOperation;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;

/**
 * This sample class demonstrates how to plug-in a new workbench view. The view
 * shows data obtained from the model. The sample creates a dummy model on the
 * fly, but a real implementation would connect to the model available either in
 * this or another plug-in (e.g. the workspace). The view is connected to the
 * model using a content provider.
 * <p>
 * The view uses a label provider to define how model objects should be
 * presented in the view. Each view can present the same model objects using
 * different labels and icons, if needed. Alternatively, a single label provider
 * can be shared between views in order to ensure that objects of the same type
 * are presented in the same way everywhere.
 * <p>
 */

public class DependencyView extends ViewPart implements
		IGraphModificationListener {

	public static final String FILTER_USAGE_RELATIONSHIPS = "FILTER_USAGE_RELATIONSHIPS";

	public static final String FILTER_ONE_TO_MANY_RELATIONSHIPS = "FILTER_ONE_TO_MANY_RELATIONSHIPS";

	public static final String FILTER_ONE_TO_ONE_RELATIONSHIPS = "FILTER_ONE_TO_ONE_RELATIONSHIPS";

	public static final String FILTER_NON_CROSS_PACKAGE_NODES = "FILTER_NON_CROSS_PACKAGE_NODES";

	public static final String INDICATE_CROSS_PACKAGE_EDGE = "INDICATE_CROSS_PACKAGE_EDGE";

	public static final String SHOW_COUPLING = "SHOW_COUPING";

	public static final String SHOW_RELATIONSHIP_NAMES = "SHOW_RELATIONSHIP_NAMES";

	public static final String FILTER_EXTENDS_RELATIONSHIPS = "FILTER_EXTENDS_RELATIONSHIPS";

	public static final String FILTER_IMPLEMENTS_RELATIONSHIPS = "FILTER_IMPLEMENTS_RELATIONSHIPS";

	/**
	 * The ID of the view as specified by the extension.
	 */
	public static final String ID = "com.artnology.independence.views.DependencyView";

	// actions
	Action findTypeAction;
	Action relayoutAction;
	Action centerGraphAction;
	DependencyGraphPropertyAction indicateCrossPackageEdge;
	FilterNonCrossPackageNodesAction filterNonCrossPackageNodes;
	//
	FilterReferencesPropertyAction filterUsesRelationships;
	FilterReferencesPropertyAction filterOneToOneRelationships;
	FilterReferencesPropertyAction filterOneToManyRelationships;
	FilterReferencesPropertyAction filterExtendsRelationships;
	FilterReferencesPropertyAction filterImplementsRelationships;
	//
	DependencyGraphPropertyAction showCoupling;
	DependencyGraphPropertyAction showRelationshipNames;
	
	long typeFilterTimestamp = 0;

	/*
	 * Undo and redo actions
	 */
	private UndoActionHandler undoAction;

	private RedoActionHandler redoAction;

	// create a unique undo context to
	// represent this view's undo history
	public static final IUndoContext undoContext = new ObjectUndoContext(ID);

	/*
	 * Operation approver for approving undo and redo
	 */
	private IOperationApprover operationApprover;

	SwingControl viewerControl = null;
	DependencyViewer dependencyViewer = null;

	/*
	 * The content provider class is responsible for providing objects to the
	 * view. It can wrap existing objects in adapters or simply return objects
	 * as-is. These objects may be sensitive to the current input of the view,
	 * or ignore it and always show the same content (like Task List, for
	 * example).
	 */

	/**
	 * The constructor.
	 * 
	 */
	public DependencyView() {
		super();
		initializeOperationHistory();

	}

	public static final DependencyView getView() {
		return (DependencyView) PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage().findView(ID);
	}

	private final void updateTitleInformation(Graph<DVNode, DVReference> graph) {
		String title = "Dependency View - ";
		title += "Displaying " + graph.getVertexCount() + " types in ";
		title += DVPackage.getNumberOfCurrentlyInvolvedPackages(graph)
				+ " packages connected by ";
		title += graph.getEdgeCount() + " references";
		setPartName(title);
	}

	/**
	 * This is a callback that will allow us to create the viewer and initialize
	 * it.
	 */
	public void createPartControl(final Composite parent) {
		SashForm mainControl = new SashForm(parent, SWT.HORIZONTAL);

		Composite navigationSearchComposite = new Composite(mainControl,
				SWT.NONE);
		GridLayout gridLayout = new GridLayout(2, false);
		navigationSearchComposite.setLayout(gridLayout);

		Label headerLabel = new Label(navigationSearchComposite, SWT.NONE);
		headerLabel
				.setText("Enter prefix or pattern (*, ?) to filter on fully qualified type name:");
		GridData headerData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		headerData.horizontalSpan = 2;
		headerLabel.setLayoutData(headerData);

		Label filterLabel1 = new Label(navigationSearchComposite, SWT.NONE);
		filterLabel1.setText("Filter types");
		final Text textFilter1 = new Text(navigationSearchComposite, SWT.SINGLE
				| SWT.BORDER);
		GridData gData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gData.grabExcessHorizontalSpace = true;
		textFilter1.setLayoutData(gData);
		textFilter1.setText("*");

		Label filterLabel2 = new Label(navigationSearchComposite, SWT.NONE);
		filterLabel2.setText("or");
		final Button textFilter2Checkbox = new Button(
				navigationSearchComposite, SWT.CHECK);

		Label filterLabel3 = new Label(navigationSearchComposite, SWT.NONE);
		filterLabel3.setText("Filter types");
		final Text textFilter2 = new Text(navigationSearchComposite, SWT.SINGLE
				| SWT.BORDER);
		gData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gData.grabExcessHorizontalSpace = true;
		textFilter2.setLayoutData(gData);
		textFilter2.setEnabled(false);

		textFilter2Checkbox.addSelectionListener(new SelectionListener() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				textFilter2.setEnabled(textFilter2Checkbox.getSelection());
				Display.getDefault().asyncExec(new Runnable() {

					@Override
					public void run() {
						executeTypeFilter(textFilter1, textFilter2);
					}

				});
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		textFilter1.addKeyListener(new KeyListener() {

			@Override
			public void keyReleased(KeyEvent arg0) {
				Display.getDefault().asyncExec(new Runnable() {

					@Override
					public void run() {
						executeTypeFilter(textFilter1, textFilter2);
					}

				});

			}

			@Override
			public void keyPressed(KeyEvent arg0) {
			}
		});
		
		textFilter1.addFocusListener(new FocusListener() {
			
			@Override
			public void focusLost(FocusEvent e) {
				
				
			}
			
			@Override
			public void focusGained(FocusEvent e) {
				
				
			}
		});

		textFilter2.addKeyListener(new KeyListener() {

			@Override
			public void keyReleased(KeyEvent arg0) {
				Display.getDefault().asyncExec(new Runnable() {

					@Override
					public void run() {
						executeTypeFilter(textFilter1, textFilter2);
					}

				});

			}

			@Override
			public void keyPressed(KeyEvent arg0) {
			}
		});

		// Create the graph and the tree viewer and register this view and the
		// viewer to listeners to graph mod events.
		DependencyGraph initialGraph = new DependencyGraph(Activator
				.getDefault().getModel());
		DVNodeTreeViewer treeViewer = new DVNodeTreeViewer(
				navigationSearchComposite);

		dependencyViewer = new DependencyViewer(initialGraph);
		initialGraph.addGraphModificationListener(this); // updating title
		initialGraph.addGraphModificationListener(dependencyViewer); // updating
																		// the
																		// graph
																		// viewer
		initialGraph.addGraphModificationListener(treeViewer); // updating the
																// package
																// structure
																// viewer

		viewerControl = new SwingControl(mainControl, SWT.NONE) {
			protected JComponent createSwingComponent() {
				return new GraphZoomScrollPane(dependencyViewer);
			}

			public Composite getLayoutAncestor() {
				return parent;
			}

		};

		mainControl.setWeights(new int[] { 30, 70 });

		// Create the help context id for the viewer's control
		PlatformUI.getWorkbench().getHelpSystem()
				.setHelp(mainControl, "test.viewer");
		makeActions();
		hookContextMenu(viewerControl);
		createGlobalActionHandlers();
		contributeToActionBars();
		updateTitleInformation(getDependencyViewer().getGraph());

	}

	public DependencyViewer getDependencyViewer() {
		return dependencyViewer;
	}
	
	private final void executeTypeFilter(Text textFilter1, Text textFilter2) {
		getDependencyViewer().getDependencyGraph().resetTypePatterns();
		getDependencyViewer().getDependencyGraph().addTypePatternString(textFilter1.getText());
		if (textFilter2.isEnabled()
				&& !textFilter2.getText().trim().isEmpty()) {
			getDependencyViewer().getDependencyGraph().addTypePatternString(textFilter2.getText());
		}
		getDependencyViewer().getDependencyGraph().filterGraph();
		typeFilterTimestamp = System.currentTimeMillis();
		Thread watchdog = new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (System.currentTimeMillis() >= (typeFilterTimestamp + 2000)) {
					
				}
				
			}
		});
		watchdog.start();
	}

	/*
	 * Create the global undo and redo action handlers.
	 */
	private void createGlobalActionHandlers() {
		// set up action handlers that operate on the current context
		undoAction = new UndoActionHandler(this.getSite(), undoContext);
		redoAction = new RedoActionHandler(this.getSite(), undoContext);
		IActionBars actionBars = getViewSite().getActionBars();
		actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(),
				undoAction);
		actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(),
				redoAction);
	}

	private void hookContextMenu(Composite mainControl) {
		MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				fillContextMenu(manager);
			}
		});
		Menu menu = menuMgr.createContextMenu(mainControl);
		mainControl.setMenu(menu);

	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalPullDown(IMenuManager manager) {
		manager.add(findTypeAction);
		manager.add(new Separator());
		manager.add(relayoutAction);
		manager.add(centerGraphAction);
		manager.add(new Separator());
		manager.add(indicateCrossPackageEdge);
		manager.add(showRelationshipNames);
		manager.add(showCoupling);
		manager.add(new Separator());
		manager.add(filterUsesRelationships);
		manager.add(filterNonCrossPackageNodes);
		manager.add(filterOneToOneRelationships);
		manager.add(filterOneToManyRelationships);
		manager.add(filterExtendsRelationships);
		manager.add(filterImplementsRelationships);
	}

	private void fillContextMenu(IMenuManager manager) {
		manager.add(findTypeAction);
		manager.add(new Separator());
		manager.add(relayoutAction);
		manager.add(centerGraphAction);
		manager.add(new Separator());
		manager.add(indicateCrossPackageEdge);
		manager.add(showRelationshipNames);
		manager.add(showCoupling);
		manager.add(new Separator());
		manager.add(filterUsesRelationships);
		manager.add(filterNonCrossPackageNodes);
		manager.add(filterOneToOneRelationships);
		manager.add(filterOneToManyRelationships);
		manager.add(filterExtendsRelationships);
		manager.add(filterImplementsRelationships);
	}

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(findTypeAction);
		manager.add(new Separator());
		manager.add(relayoutAction);
		manager.add(centerGraphAction);
		manager.add(new Separator());
		manager.add(indicateCrossPackageEdge);
		manager.add(showRelationshipNames);
		manager.add(showCoupling);
		manager.add(new Separator());
		manager.add(filterUsesRelationships);
		manager.add(filterNonCrossPackageNodes);
		manager.add(filterOneToOneRelationships);
		manager.add(filterOneToManyRelationships);
		manager.add(filterExtendsRelationships);
		manager.add(filterImplementsRelationships);

	}
	
	
	
	private class FilterNonCrossPackageNodesAction extends DependencyGraphPropertyAction {

		public FilterNonCrossPackageNodesAction(String title, String property,
				String imagePath, boolean initialState)
				throws IllegalArgumentException {
			super(title, property, imagePath, initialState);
		}
		
		@Override
		public void run() {
			super.run();

			IUndoableOperation operation = null;
			if (Activator.getDefault().getPrefStore().getBoolean(getProperty())) {
				operation = new RemoveNodeFromGraphOperation("Filter non cross Package Types", getDependencyViewer().getDependencyGraph(), (Collection<DVNode>) getGraphModel().getAllNonCrossPackageTypes());
			} else {
				operation = new AddNodeToGraphOperation("Remove Filter non cross Package Types", getDependencyViewer().getDependencyGraph(), (Collection<DVNode>) getGraphModel().getAllNonCrossPackageTypes());
			}
			operation.addContext(DependencyView.undoContext);
			try {
				DependencyView.getOperationHistory().execute(operation, null, null);
			} catch (ExecutionException e) {
				e.printStackTrace();
			}

		}
		
	}

	private class FilterReferencesPropertyAction extends DependencyGraphPropertyAction {

		private Class<? extends DVReference> filterReferenceType = null;

		public FilterReferencesPropertyAction(String title, String property,
				String imagePath, boolean initialState,
				Class<? extends DVReference> filterReferenceType)
				throws IllegalArgumentException {
			super(title, property, imagePath, initialState);
			this.filterReferenceType = filterReferenceType;
		}

		@Override
		public void run() {
			super.run();

			Graph<DVNode, DVReference> manipulatedGraph = getDependencyViewer()
					.getGraph();
			if (Activator.getDefault().getPrefStore().getBoolean(getProperty())) {
				Set<Class<? extends DVReference>> filterReferenceTypes = new HashSet<Class<? extends DVReference>>();
				filterReferenceTypes.add(filterReferenceType);
				manipulatedGraph = DVReference.getEdgePredicateFilter(
						filterReferenceTypes).transform(manipulatedGraph);
			} else {
				for (DVNode node : Activator.getDefault().getModel()
						.getAllTypes()) {
					if (getDependencyViewer().getGraph().containsVertex(node)) {
						for (DVReference ref : node.getAllIncidentReferences()) {
							if (ref.getClass().equals(filterReferenceType)) {
								manipulatedGraph.addEdge(ref,
										ref.getReferenceFromNode(),
										ref.getReferencedNode(),
										EdgeType.DIRECTED);

							}
						}
					}
				}

			}
			getDependencyViewer().getDependencyGraph().setGraph(
					manipulatedGraph);

		}

	}
	
	private class DependencyGraphPropertyAction extends BooleanPropertyAction {

		private String property = null;

		public DependencyGraphPropertyAction(String title, String property,
				String imagePath, boolean initialState)
				throws IllegalArgumentException {
			super(title, Activator.getDefault().getPrefStore(), property);
			setImageDescriptor(Activator.getImageDescriptor(imagePath));
			setToolTipText(title);
			Activator.getDefault().getPrefStore()
					.setDefault(property, initialState);
			Activator.getDefault().getPrefStore()
					.setValue(property, initialState);
			setChecked(initialState);
			this.property = property;
		}
		
		

		public String getProperty() {
			return property;
		}



		@Override
		public void run() {
			Activator.getDefault().getPrefStore()
					.setValue(getProperty(), isChecked());
			getDependencyViewer().repaint();
			
		}

	}


	private void makeActions() {
		findTypeAction = new Action("Find type in graph",
				Action.AS_PUSH_BUTTON) {

			@Override
			public void run() {
				Display.getDefault().syncExec(new Runnable() {

					@Override
					public void run() {
						FindTypeInGraphHandler handler = new FindTypeInGraphHandler();
						try {
							handler.execute(null);
						} catch (ExecutionException e) {
							e.printStackTrace();
						}
					}

				});
			}

		};
		findTypeAction.setToolTipText("Find type in graph");
		findTypeAction.setImageDescriptor(Activator
				.getImageDescriptor("icons/findNode.gif"));
		
		relayoutAction = new Action("Re-layout current Graph",
				Action.AS_PUSH_BUTTON) {

			@Override
			public void run() {
				Display.getDefault().syncExec(new Runnable() {

					@Override
					public void run() {
						getDependencyViewer().createGraphLayout(getDependencyViewer().getDependencyGraph());
					}

				});
			}

		};
		relayoutAction.setToolTipText("Re-layout current Graph");
		relayoutAction.setImageDescriptor(Activator
				.getImageDescriptor("icons/relayoutButton.gif"));
		
		centerGraphAction = new Action("Center Graph in View Area",
				Action.AS_PUSH_BUTTON) {

			@Override
			public void run() {
				Display.getDefault().syncExec(new Runnable() {

					@Override
					public void run() {
						getDependencyViewer().translateAndScale();
					}

				});
			}

		};
		centerGraphAction.setToolTipText("Center Graph in View Area");
		centerGraphAction.setImageDescriptor(Activator
				.getImageDescriptor("icons/centerGraphOnViewer.gif"));

		indicateCrossPackageEdge = new DependencyGraphPropertyAction(
				"Highlight cross package relationships",
				INDICATE_CROSS_PACKAGE_EDGE, "icons/packageRelationship.gif",
				false);
		// filter section
		filterNonCrossPackageNodes = new FilterNonCrossPackageNodesAction(
				"Show only cross package nodes",
				FILTER_NON_CROSS_PACKAGE_NODES,
				"icons/filterNonPackageRelationshipNodes.gif", false);
		filterUsesRelationships = new FilterReferencesPropertyAction(
				"Filter Usage Relationships", FILTER_USAGE_RELATIONSHIPS,
				"icons/usage.gif", false, DVUsesRelationship.class);
		filterOneToOneRelationships = new FilterReferencesPropertyAction(
				"Filter 1:1 Relationships", FILTER_ONE_TO_ONE_RELATIONSHIPS,
				"icons/oneToOne.gif", false, DVOneToOneRelationship.class);

		filterOneToManyRelationships = new FilterReferencesPropertyAction(
				"Filter 1:n Relationships", FILTER_ONE_TO_MANY_RELATIONSHIPS,
				"icons/oneToMany.gif", false, DVOneToManyRelationship.class);

		filterExtendsRelationships = new FilterReferencesPropertyAction(
				"Filter Extends Relationships", FILTER_EXTENDS_RELATIONSHIPS,
				"icons/extends.gif", false, DVExtensionRelationship.class);
		filterImplementsRelationships = new FilterReferencesPropertyAction(
				"Filter Implements Relationships",
				FILTER_IMPLEMENTS_RELATIONSHIPS, "icons/filterImplements.gif",
				false, DVImplementsRelationship.class);
		showRelationshipNames = new DependencyGraphPropertyAction(
				"Show Relationship Names", SHOW_RELATIONSHIP_NAMES,
				"icons/showNames.gif", false);

		// coupling
		showCoupling = new DependencyGraphPropertyAction("Show Coupling",
				SHOW_COUPLING, "icons/showCoupling.gif", false);
	}

	@Override
	public void setFocus() {
		viewerControl.setFocus();
	}

	public static DVGraphModel getGraphModel() {
		return DependencyView.getView().getDependencyViewer()
				.getDependencyGraph().getGraphModel();
	}

	@Override
	public void graphUpdated(final DependencyGraph updatedGraph) {
		Display.getDefault().syncExec(new Runnable() {
			@Override
			public void run() {
				updateTitleInformation(updatedGraph.getGraph());
			}
		});

	}

	/*
	 * Initialize the workbench operation history for our undo context.
	 */
	private void initializeOperationHistory() {

		// Install an operation approver for this undo context that prompts
		// according to a user preference.
		operationApprover = new LinearUndoEnforcer();
		getOperationHistory().addOperationApprover(operationApprover);
	}

	/*
	 * Get the operation history from the workbench.
	 */
	public static final IOperationHistory getOperationHistory() {
		return PlatformUI.getWorkbench().getOperationSupport()
				.getOperationHistory();
	}

}